package log

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"time"
)

type LogLevel int

const (
	ALERT LogLevel = iota
	ERROR
	IERROR
	NOTICE
	INFO
	DEBUG
	INHERIT
)
const (
	LogAlert  LogLevel = LogLevel(ALERT)
	LogError  LogLevel = LogLevel(ERROR)
	LogIError LogLevel = LogLevel(IERROR)
	LogNotice LogLevel = LogLevel(NOTICE)
	LogInfo   LogLevel = LogLevel(INFO)
	LogDebug  LogLevel = LogLevel(DEBUG)
)

type logItem struct {
	Stack []uintptr
	Time  time.Time
	Level LogLevel
	//Params []interface{}
	Msg string
}

type logger struct {
	level          LogLevel
	filename       string
	queue          chan *logItem
	done           chan bool
	PrintToConsole bool // 控制是否打印到控制台
}

var logItemPool = sync.Pool{
	New: func() interface{} {
		return &logItem{
			Stack: make([]uintptr, 4),
		}
	},
}

type stringer interface {
	String() string
}

func NewLogger(processName string, queueSize int) (*logger, error) {
	filename := generateFilename(processName)
	file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return nil, err
	}

	l := &logger{
		filename:       filename,
		queue:          make(chan *logItem, queueSize),
		done:           make(chan bool),
		PrintToConsole: true,
	}
	l.SetLevel(LogDebug)
	go l.processQueue(file)

	return l, nil
}

func (l *logger) SetLevel(lvl LogLevel) {
	l.level = lvl
}

func (l *logger) Enabled(level LogLevel) bool {
	return level <= l.level
}

func (l *logger) lvlString(level LogLevel) string {
	switch level {
	case ALERT:
		return "[A]"
	case ERROR:
		return "[E]"
	case IERROR:
		return "[I]"
	case NOTICE:
		return "[N]"
	case INFO:
		return "[I]"
	case DEBUG:
		return "[D]"
	case INHERIT:
		return "[I]"
	}
	return ""
}
func (l *logger) Log(level LogLevel, format ...interface{}) {
	if !l.Enabled(level) {
		return
	}
	// 创建日志结构体
	item := logItemPool.Get().(*logItem)
	item.Level = level
	item.Time = time.Now().Local()
	item.Msg = l.toString(format...)
	runtime.Callers(3+1, item.Stack)

	// 投递到日志协程输出
	l.queue <- item

	if l.PrintToConsole {
		l.printToConsole(item)
	}
}
func (l *logger) printToConsole(item *logItem) {
	timestamp := time.Now().Format("2006-01-02 15:04:05")
	fmt.Printf("[%s] %v %s\n", timestamp, l.lvlString(item.Level), item.Msg)
}

func (l *logger) Close() {
	close(l.queue)
	<-l.done
}
func (l *logger) processQueue(file *os.File) {
	for item := range l.queue { //一直读到queue读取全部完成
		timestamp := time.Now().Format("2006-01-02 15:04:05")
		logMessage := fmt.Sprintf("[%s] %v %s\n", timestamp, l.lvlString(item.Level), item.Msg)
		file.WriteString(logMessage)
	}

	file.Close()
	l.done <- true
}
func generateFilename(processName string) string {
	currentTime := time.Now()
	date := currentTime.Format("20060102")
	filename := fmt.Sprintf("%s_%s.log", processName, date)
	return filepath.Join(".", filename)
}

func (l *logger) Info2(v ...interface{}) {
	l.Log2(v...)
}

func (l *logger) Log2(params ...interface{}) {
	// 创建日志结构体
	item := logItemPool.Get().(*logItem)
	item.Time = time.Now().Local()
	//item.Params = unwrap(params...)
	// 这里使用调用者协程格式化日志输出，原因是为了防止多协程访问导致程序崩溃
	item.Msg = l.formatString(unwrap(params...)...)
	runtime.Callers(3+1, item.Stack)

	// 投递到日志协程输出
	l.queue <- item
}

func unwrap(args ...interface{}) []interface{} {
	head := args[0]
	switch head.(type) {
	case func() (string, []interface{}):
		msg, args := head.(func() (string, []interface{}))()
		args = unwrap(args...)
		return append([]interface{}{msg}, args...)
	case func() []interface{}:
		args = unwrap(head.(func() []interface{})()...)
	case func(...interface{}) []interface{}:
		args = unwrap(head.(func(...interface{}) []interface{})(args[1:]...)...)
	}
	return args
}

func (l *logger) toString(params ...interface{}) string {
	if s, ok := params[0].(string); ok {
		return fmt.Sprintf(s, params[1:]...)
	} else if s, ok := params[0].(stringer); ok {
		return fmt.Sprintf(s.String(), params[1:]...)
	} else if len(params) == 1 {
		return fmt.Sprintf("%v", params[0])
	} else {
		return fmt.Sprintf(fmt.Sprintf("%s", params[0]), params[1:]...)
	}
	return ""
}

func (l *logger) formatString(params ...interface{}) string {
	if s, ok := params[0].(string); ok {
		return fmt.Sprintf(s, params[1:]...)
	} else if s, ok := params[0].(stringer); ok {
		return fmt.Sprintf(s.String(), params[1:]...)
	} else {
		return fmt.Sprintf(fmt.Sprintf("%s", params[0]), params[1:]...)
	}
	return ""
}

func (l *logger) Debug(params ...interface{})   { l.Log(DEBUG, params...) }
func (l *logger) Info(params ...interface{})    { l.Log(INFO, params...) }
func (l *logger) Notice(params ...interface{})  { l.Log(NOTICE, params...) }
func (l *logger) IError(params ...interface{})  { l.Log(IERROR, params...) }
func (l *logger) Error(params ...interface{})   { l.Log(ERROR, params...) }
func (l *logger) Alert(params ...interface{})   { l.Log(ALERT, params...) }
func (l *logger) Printf(params ...interface{})  { l.Log(INFO, params...) }
func (l *logger) Println(params ...interface{}) { l.Log(INFO, params...) }
